---
title: "Interactive Maps with Leaflet in R"
author: "Tony D"
execute:
warning: false
error: false
format:
html:
toc: true
toc-location: right
code-fold: show
code-tools: true
number-sections: true
code-block-bg: true
code-block-border-left: "#31BAE9"
code-copy: true
---
This document provides a comprehensive guide to creating interactive maps using the `leaflet` package in R. We will explore various features of `leaflet`, from basic map creation to advanced customizations like adding markers, popups, and choropleth layers. This guide is intended for users who have a basic understanding of R and are interested in data visualization and geospatial analysis.
# Getting Started: Loading Libraries and Data
First, we need to load the necessary R libraries. We'll be using `tidyverse` for data manipulation, `sf` for working with spatial data, `leaflet` for creating the maps, and `geojsonio` for reading GeoJSON files.
```{r}
# Load the tidyverse package for data manipulation and visualization.
library(tidyverse)
# Load the sf package for working with simple features, a standardized way to encode spatial vector data.
library(sf)
# Load the leaflet package for creating interactive maps.
library(leaflet)
# Load the geojsonio package for reading and writing GeoJSON.
library(geojsonio)
# Load the leaflet.extras package for additional leaflet features.
library(leaflet.extras)
```
# Displaying a Basic Map with OpenStreetMap
Here, we'll create a simple map using the default OpenStreetMap tiles and add a marker for a specific location.
```{r}
# Create a leaflet map widget.
m <- leaflet() %>%
# Add the default OpenStreetMap map tiles.
addTiles() %>%
# Add a marker to the map at the specified longitude and latitude.
addMarkers(lng=174.768, lat=-36.852, popup="The birthplace of R")
# Display the map.
m
```
# Using Google Maps as a Basemap
Instead of the default OpenStreetMap, you can use other tile providers like Google Maps.
```{r}
leaflet() |>
# Add Google Maps tiles using the specified URL template.
addTiles(urlTemplate = "https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}") |>
# Set the initial view of the map with a specific center and zoom level.
setView(116.347817690225, 39.997202126977, zoom = 16) |>
# Add a marker to the map.
addMarkers(116.347817690225, 39.997202126977)
```
# Using Third-Party Map Providers
Leaflet supports a wide variety of third-party map providers. You can find a list of available providers in the `providers` object.
```{r}
# You can uncomment these lines to try a different map provider.
# m <- leaflet() %>% setView(lng = -71.0589, lat = 42.3601, zoom = 10)
# m %>% addProviderTiles(providers$Stadia.StamenToner)
```
# Adding Popups
Popups are small boxes containing arbitrary HTML that point to a specific point on the map.
```{r}
# Create HTML content for the popup.
content <- paste(sep = "<br/>",
"<b><a href='https://www.samurainoodle.com/'>Samurai Noodle</a></b>",
"606 5th Ave. S",
"Seattle, WA 98138"
)
# Create a map and add a popup.
leaflet() %>% addTiles() %>%
setView(-122.327298, 47.597131,zoom = 12) %>%
addPopups(-122.327298, 47.597131, content,
# You can customize the popup options, for example, to remove the close button.
options = popupOptions(closeButton = FALSE)
)
```
# Adding Markers from a Data Frame
You can add multiple markers to a map from a data frame.
```{r}
# Load the htmltools package for working with HTML.
library(htmltools)
# Create a data frame with marker information.
df <- read.csv(textConnection(
"Name,Lat,Long
Samurai Noodle,47.597131,-122.327298
Kukai Ramen,47.6154,-122.327157
Tsukushinbo,47.59987,-122.326726"
))
# Create a map and add markers from the data frame.
leaflet(df) %>% addTiles() %>%
addMarkers(~Long, ~Lat, popup = ~htmlEscape(Name))
```
# Adding Labels
Labels are similar to popups but are always displayed or displayed on mouseover.
```{r}
# Load the htmltools package.
library(htmltools)
# Create a data frame with marker information.
df <- read.csv(textConnection(
"Name,Lat,Long
Samurai Noodle,47.597131,-122.327298
Kukai Ramen,47.6154,-122.327157
Tsukushinbo,47.59987,-122.326726"))
# Create a map and add markers with labels.
leaflet(df) %>% addTiles() %>%
addMarkers(~Long, ~Lat, label = ~htmlEscape(Name))
```
# Creating a World Choropleth Map
Now, let's create a choropleth map of the world, where countries are colored based on their per capita GDP.
## Loading Geospatial and GDP Data
We'll start by loading the world administrative boundaries from a GeoJSON file and then merge it with per capita GDP data from an Excel file.
```{r}
# The maptools package is now archived, so we are not using it.
# install.packages("https://cran.r-project.org/src/contrib/Archive/maptools/maptools_1.1-8.tar.gz")
```
```{r}
# Read the world administrative boundaries from a GeoJSON file.
json_data=read_sf("world-administrative-boundaries.geojson")
```
```{r}
# Convert the sf object to a data frame.
map_df <- as.data.frame(json_data)
```
```{r}
# This is an example of how you could create a data frame manually.
# name<-c("Ghana", "Grenada", "Guyana", "India", "Jamaica", "Kenya", "United States","Canada")
# val<-c(1,2,4,5,5,1000,20000, 100)
# per_gdp_usd<-data.frame(name,val)
```
```{r}
# Load the openxlsx and readxl packages for reading Excel files.
library(openxlsx)
library(readxl)
# Read the world GDP data from an Excel file and clean up the country names to match the GeoJSON data.
per_gdp_usd=read_excel('world data.xlsx') %>% mutate(
name=case_when(
name == "United States" ~ "United States of America",
name == "Russia" ~ "Russian Federation",
name == "U.K. of Great Britain and Northern Ireland" ~ "United Kingdom",
name == "South Korea"~ "Republic of Korea",
name == "Lao People's Democratic Republic" ~ "Laos",
TRUE ~ name
)
)
```
## Merging and Preparing the Data
We'll merge the geospatial data with the GDP data and convert it back to an `sf` object.
```{r}
# These lines are for debugging and checking the data.
# test=full_join(map_df, per_gdp_usd, by="name")
# left =test %>% filter(is.na(iso3)==TRUE)
# right=test %>% filter(is.na(per_gdp_total)==TRUE)
```
```{r}
# Merge the map data frame with the GDP data frame by country name.
map_df002 <- merge(map_df, per_gdp_usd, by.x = "name", by.y = "name")
```
```{r}
# Get a glimpse of the merged data frame.
glimpse(map_df002)
```
```{r}
# Convert the merged data frame back to an sf object.
map_sf002 <- sf::st_as_sf(map_df002, sf_column_name = "geometry")
```
## Creating the Choropleth Map
Now we can create the choropleth map.
```{r}
# Create a color palette for the choropleth map.
pal <- colorNumeric("viridis", NULL)
# Create the leaflet map.
leaflet(map_sf002) %>%
addTiles() %>%
# Add polygons to the map, representing the countries.
addPolygons(smoothFactor = 0.3, fillOpacity = 0.5,weight = 1,
# Set the fill color based on the per capita GDP.
fillColor = ~ pal(per_gdp_usd),
# Create a popup with information about the country.
popup = ~ paste0(
"地区:", name, "<br/>",
"<hr/>",
"人均gpd:", per_gdp_usd, "(千 美元)", "<br/>"
),
# Create a label with more detailed information.
label = lapply(paste0(
"地区:", "<b>", map_sf002$name, "</b>", "<br/>",
"人均gpd 美元:", round(map_sf002$per_gdp_usd/1000), "K <br/>",
"总gpd 美元:", round(map_sf002$per_gdp_total/1000000), "M <br/>"
), htmltools::HTML)
) %>%
# Add a legend to the map.
addLegend(
position = "bottomright", title = "人均gpd(美元)",
pal = pal, values = ~per_gdp_usd, opacity = 1.0
)
```
# China Maps
Now we will create several maps of China, including a map of a single city, a choropleth map of all provinces, and a map of a single province with its cities.
## China One City Map (Shenzhen)
Here, we'll create a map of Shenzhen.
```{r}
# Read the GeoJSON data for Shenzhen.
json_data <- sf::read_sf("./GeoMapData_CN/citys/440300.json")
# You can also load the data directly from a URL.
# json_data=read_sf("https://geo.datav.aliyun.com/areas_v2/bound/440300_full.json")
```
Shenzhen City:
```{r}
# Create a color palette.
pal <- colorNumeric("viridis", NULL)
# Create the map of Shenzhen.
leaflet(json_data) %>%
addTiles() %>%
addPolygons(smoothFactor = 0.3, fillOpacity = 0.1)
```
## All China Each Province GDP Map
Next, we'll create a choropleth map of China's provinces, colored by their per capita GDP in 2022.
```{r}
# Read the GeoJSON data for China's provinces.
json_data=read_sf("https://geo.datav.aliyun.com/areas_v2/bound/100000_full.json")
```
China 2022 Per Capita GDP by Province (USD):
```{r}
# Load the openxlsx and readxl packages.
library(openxlsx)
library(readxl)
# Read the GDP data for China's provinces.
per_gdp_usd=read_excel('china gdp2022.xlsx')
```
```{r}
# Convert the sf object to a data frame.
map_df <- as.data.frame(json_data)
```
```{r}
# Merge the map data with the GDP data.
map_df002 <- merge(map_df, per_gdp_usd, by.x = "name", by.y = "city")
```
```{r}
# Convert the merged data frame back to an sf object.
map_sf002 <- sf::st_as_sf(map_df002, sf_column_name = "geometry")
```
```{r}
# Create a color palette.
pal <- colorNumeric("viridis", NULL)
# Create the choropleth map of China's provinces.
leaflet(map_sf002) %>%
addTiles() %>%
addPolygons(smoothFactor = 0.3, fillOpacity = 0.5,weight = 1,
fillColor = ~ pal(per_gpd_usd),
popup = ~ paste0(
"地区:", name, "<br/>",
"<hr/>",
"人均gpd:", per_gpd_usd, "(美万)", "<br/>"
),
label = lapply(paste0(
"地区:", "<b>", map_sf002$name, "</b>", "<br/>",
"人均gpd 美元:", map_sf002$per_gpd_usd, "<br/>",
"人均gpd 人民币:", map_sf002$per_gpd_rmb, "<br/>",
"人口:", round(map_sf002$total_gpd_rmb/map_sf002$per_gpd_rmb), "<br/>"
), htmltools::HTML)
) %>% addLegend(
position = "bottomright", title = "人均gpd(美元)",
pal = pal, values = ~per_gpd_usd, opacity = 1.0
)
```
GDP data source: National Bureau of Statistics of China
## China One Province Map with Each City's GDP (Guangdong)
Here, we'll create a choropleth map of Guangdong province, with each city colored by its per capita GDP in 2021.
```{r}
# Read the GeoJSON data for Guangdong province.
json_data <- sf::read_sf("./GeoMapData_CN/province/440000.json")
```
Guangdong 2021 Per Capita GDP by City (USD):
```{r}
# Load the openxlsx and readxl packages.
library(openxlsx)
library(readxl)
# Read the GDP data for Guangdong's cities.
per_gdp_usd=read_excel('guangdong city gdp2021.xlsx')
```
```{r}
# Convert the sf object to a data frame.
map_df <- as.data.frame(json_data)
```
```{r}
# Merge the map data with the GDP data.
map_df002 <- merge(map_df, per_gdp_usd, by.x = "name", by.y = "city")
```
```{r}
# Convert the merged data frame back to an sf object.
map_sf002 <- sf::st_as_sf(map_df002, sf_column_name = "geometry")
```
Data source: Guangdong Statistical Yearbook 2022
```{r}
# Create a color palette.
pal <- colorNumeric("viridis", NULL)
# Create the choropleth map of Guangdong province.
leaflet(map_sf002) %>%
addTiles() %>%
addPolygons(smoothFactor = 0.3, fillOpacity = 0.5,weight = 1,
fillColor = ~ pal(per_gpd_usd),
popup = ~ paste0(
"城市:", name, "<br/>",
"<hr/>",
"人均gpd:", per_gpd_usd, "(美万)", "<br/>"
),
label = lapply(paste0(
"城市:", "<b>", map_sf002$name, "</b>", "<br/>",
"人均gpd 美元:", map_sf002$per_gpd_usd, "<br/>",
"人均gpd 人民币:", map_sf002$per_gpd_rmb, "<br/>",
"人口:", round(map_sf002$total_gpd_rmb*10000000/map_sf002$per_gpd_rmb), "<br/>"
), htmltools::HTML)
) %>% addLegend(
position = "bottomright", title = "人均gpd(美元)",
pal = pal, values = ~per_gpd_usd, opacity = 1.0
)
```
https://zh.wikipedia.org/wiki/%E5%B9%BF%E4%B8%9C%E5%90%84%E5%9C%B0%E7%BA%A7%E5%B8%82%E5%9C%B0%E5%8C%BA%E7%94%9F%E4%BA%A7%E6%80%BB%E5%80%BC%E5%88%97%E8%A1%A8
# China Province Map
This section shows how to create a simple map of China's provinces and add markers for the provincial capitals.
```{r}
# Load the leaflet and sf packages.
library(leaflet)
library(sf)
# Read the GeoJSON data for China's provinces.
json_data <- sf::read_sf("./GeoMapData_CN/china.json")
# You can also load the data from a URL.
# json_data=read_sf("https://geo.datav.aliyun.com/areas_v2/bound/100000_full.json")
# Create a color palette.
pal <- colorNumeric("viridis", NULL)
# Create the map of China's provinces.
m=leaflet(json_data) %>%
addTiles() %>%
addPolygons(smoothFactor = 0.3, fillOpacity = 0.1)
# Display the map.
m
```
## Add Provincial Capitals
Now, let's add the provincial capitals to the map.
```{r}
# Read the GeoJSON data for China's provinces.
China=read_sf("https://geo.datav.aliyun.com/areas_v2/bound/100000_full.json")
```
```{r}
# Extract the coordinates of the provincial capitals.
China002=China %>% as_data_frame() %>% mutate(
center2=as.character(center) %>% str_replace('c','_')%>% str_replace('[(]','') %>% str_replace('[)]','')
)
```
```{r}
# Separate the coordinates into x and y columns.
China003=China002%>%separate(center2, c("x", "y"), ", ")
```
```{r}
# Add the provincial capitals to the map.
China_point = China003 %>%
slice(-35)
m %>%
addCircles(data = China_point,
lng = ~as.numeric(x), lat = ~as.numeric(y),color = "red",weight = 10,
fillOpacity =2)
```
# China Province and City Map
This section demonstrates how to load and combine GeoJSON files for all provinces and their cities.
```{r}
# Get a list of all province GeoJSON files.
map_list <- lapply(
X = list.files("./GeoMapData_CN/province", recursive = T, full.names = T),
FUN = sf::read_sf
)
# Print the number of files.
length(map_list)
```
```{r}
# Check for any files that don't have 10 columns.
for (i in c(1:length(map_list))){
#print(i)
#print(dim(map_list[[i]]))
#print(ncol(map_list[[i]]))
if(ncol(map_list[[i]])!=10){print(map_list[[i]])}
}
```
```{r}
# Exclude some problematic files.
X = list.files("./GeoMapData_CN/province", recursive = T, full.names = T)
X2=X[-which(X %in% c("./GeoMapData_CN/province/710000.json"
))]
# Reload the list of files.
map_list <- lapply(
X = X2,
FUN = sf::read_sf
)
```
```{r}
# Print the number of files.
length(map_list)
```
```{r}
# Combine all the sf objects into one.
province_map <- Reduce("rbind", map_list)
```
```{r}
# This code would display the combined map.
# library(leaflet)
# library(sf)
#
# json_data <- province_map
#
#
# pal <- colorNumeric(c("red", "green", "blue"), 1:10)
#
# leaflet(json_data) %>%
# addTiles() %>%
# addPolygons(smoothFactor = 0.3, fillOpacity = 0.1)
```
# China City and District Map
This section is similar to the previous one, but it loads GeoJSON files for cities and their districts.
```{r}
# Get a list of all city GeoJSON files.
map_list <- lapply(
X = list.files("./GeoMapData_CN/citys", recursive = T, full.names = T),
FUN = sf::read_sf
)
# Print the number of files.
length(map_list)
```
```{r}
# Check for any files that don't have 10 columns.
for (i in c(1:length(map_list))){
#print(i)
#print(dim(map_list[[i]]))
#print(ncol(map_list[[i]]))
if(ncol(map_list[[i]])!=10){print(map_list[[i]])}
}
```
```{r}
# Exclude some problematic files.
X = list.files("./GeoMapData_CN/citys", recursive = T, full.names = T)
X2=X[-which(X %in% c("./GeoMapData_CN/citys/620200.json",
"./GeoMapData_CN/citys/460400.json",
"./GeoMapData_CN/citys/442100.json",
"./GeoMapData_CN/citys/442000.json",
"./GeoMapData_CN/citys/441900.json"
))]
# Reload the list of files.
map_list <- lapply(
X = X2,
FUN = sf::read_sf
)
```
```{r}
# Print the number of files.
length(map_list)
```
```{r}
# Combine all the sf objects into one.
province_map <- Reduce("rbind", map_list)
```
```{r}
# This code would display the combined map.
# library(leaflet)
# library(sf)
#
# json_data <- province_map
#
#
# pal <- colorNumeric(c("red", "green", "blue"), 1:10)
#
# leaflet(json_data) %>%
# addTiles() %>%
# addPolygons(smoothFactor = 0.3, fillOpacity = 0.1)
```
# Displaying All Map Providers
This section shows how to list all available map providers in the `leaflet` package.
```{r, attr.output='.details summary="providers"'}
# Display the list of available map providers.
providers
```
# Session Information
This section provides information about the current R session, including the versions of the loaded packages.
```{r, attr.output='.details summary="sessionInfo()"'}
# Display the session information.
sessionInfo()
```
# Resources
Here are some useful resources for learning more about `leaflet` and working with geospatial data in R.
https://rstudio.github.io/leaflet/
https://github.com/Lchiffon/leafletCN
https://github.com/longwosion/geojson-map-china
https://xiangyun.rbind.io/2022/02/draw-china-maps/
https://datav.aliyun.com/portal/school/atlas/area_selector